package ua.lviv.franko.mlsFunctions.oneDimension;

import java.util.ArrayList;
import java.util.Iterator;

import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

import ua.lviv.franko.FEMShapeFunction1D.FEMShapeFunction1D1;
import ua.lviv.franko.FEMShapeFunction1D.IFEM1D;
import ua.lviv.franko.integration.IFunction1D;
import ua.lviv.franko.matrix.RowType;
import ua.lviv.franko.matrix.hash.lu.HashMatrixLUStyle;
import ua.lviv.franko.matrix.hash.lu.LUFactorization;
import ua.lviv.franko.mlsFunctions.oneDimension.weightFunctions.CubicSpline;
import ua.lviv.franko.mlsFunctions.oneDimension.weightFunctions.IWeightFunction;
import ua.lviv.franko.solvers.oneDimention.HasBaseNode;
import ua.lviv.franko.visualisation.NewOkCancelDialog;

public class BaseFunctionCoupled implements ICalculate, IFunction1D, HasBaseNode {

	protected int					PolinomialDegree;
	protected Particle				xi;
	protected IWeightFunction		func;
	protected HashMatrixLUStyle			A;
	protected double[]				b;
	protected ArrayList<Particle>	NodeList;
	protected ArrayList<IFEM1D>		femList;
	protected double				EPS	= 0.0000001;

	public BaseFunctionCoupled(int PolinomialDegree, Particle xi, IWeightFunction func, ArrayList<IFEM1D> femList) {
		this.PolinomialDegree = PolinomialDegree;
		this.xi = xi;
		this.func = func;
		this.femList = femList;
	}

	public double calculate(double x) {
		if ((xi.xi - xi.rho > x) || (xi.xi + xi.rho < x)) {
			return 0;
		}
		ArrayList<Double> BaseVector = getBaseVector(xi.xi, xi);
		int n = BaseVector.size();
		b = new double[n];
		// формуємо матрицю Грамма М
		A = new HashMatrixLUStyle(RowType.ARRAY_TYPE, n);
		for (int i = 0; i < NodeList.size(); i++) {
			Particle particle = NodeList.get(i);
			calculateAlfa(x, particle);
		}

		double val = func.calculate(x, xi.xi, xi.rho);
		// BaseVector = getBaseVector(x, new Particle(x, 0.0));
		boolean key = false;
		for (int i = 0; i < b.length; i++) {
			b[i] = BaseVector.get(i) * val;
			if (Math.abs(b[i]) > EPS) {
				key = true;
			}
		}

		double returnValue = 0.0;
		if (key) {
			LUFactorization lu = new LUFactorization(A);
			double[] res = lu.calculate(b);

			// BaseVector = getBaseVector(xi.xi, xi);
			double[] femInfluence = this.calculateFEM(x);
			BaseVector = getBaseVector(x, new Particle(x, 0.0));
			for (int i = 0; i < b.length; i++) {
				returnValue += (BaseVector.get(i) - femInfluence[i]) * res[i];
			}
		}
		if (Double.isNaN(returnValue)) {
			return 0;
		}
		return returnValue;
	}

	@Override
	public double[] calculateAll(double x) {
		if ((xi.xi - xi.rho > x) || (xi.xi + xi.rho < x)) {
			return new double[] { 0.0, 0.0 };
		}
		ArrayList<Double> BaseVector = getBaseVector(xi.xi, xi);
		int n = BaseVector.size();
		b = new double[n];
		// формуємо матрицю Грамма М
		A = new HashMatrixLUStyle(RowType.ARRAY_TYPE, n);
		for (int i = 0; i < NodeList.size(); i++) {
			Particle particle = NodeList.get(i);
			calculateAlfa(x, particle);
		}

		double val = func.calculate(x, xi.xi, xi.rho);
		// BaseVector = getBaseVector(x, new Particle(x, 0.0));
		boolean key = false;
		for (int i = 0; i < b.length; i++) {
			b[i] = BaseVector.get(i) * val;
			if (Math.abs(b[i]) > EPS) {
				key = true;
			}
		}

		double returnValue = 0.0;
		double returnValueDer = 0.0;
		if (key) {
			LUFactorization lu = new LUFactorization(A);
			double[] res = lu.calculate(b);
			double[] femInfluence = this.calculateFEM(x);
			BaseVector = getBaseVector(x, new Particle(x, 0.0));
			for (int i = 0; i < b.length; i++) {
				returnValue += (BaseVector.get(i) - femInfluence[i]) * res[i];
			}
			// val = func.evaluateDerivaty((x-xii)/rho);
			key = false;
			double[] tmp = new double[b.length];
			for (int j = 0; j < this.NodeList.size(); j++) { // по вузлах
				Particle xj = NodeList.get(j);
				val = func.calculateDerivaty(x, xj.xi, xj.rho);// func.evaluateDerivaty((x-xj.x)/xj.rho);
				BaseVector = this.getBaseVector(xj.xi, xj);
				double tmpVal = 0.0;
				for (int k = 0; k < res.length; k++) {// альфа на вектор Р і
														// вагу ...
					tmpVal += BaseVector.get(k) * res[k];
				}

				for (int k = 0; k < res.length; k++) {// число на вектор Р
					tmp[k] += BaseVector.get(k) * tmpVal * val;
				}
			}
			val = func.calculateDerivaty(x, xi.xi, xi.rho); // func.evaluateDerivaty((x-xii)/rho);
			BaseVector = this.getBaseVector(xi.xi, xi);
			for (int i = 0; i < b.length; i++) {
				b[i] = BaseVector.get(i) * val - tmp[i];
				if (Math.abs(b[i]) > EPS) {
					key = true;
				}
			}
			BaseVector = this.getBaseVectorDerivaty(x, new Particle(x, 0.0));
			femInfluence = this.calculateDerivatyFEM(x);
			for (int j = 0; j < res.length; j++) {
				returnValueDer += (BaseVector.get(j) - femInfluence[j]) * res[j];
			}
			if (key) {
				double[] res2 = lu.calculate(b);

				BaseVector = this.getBaseVector(x, new Particle(x, 0.0));
				femInfluence = this.calculateFEM(x);
				for (int j = 0; j < res2.length; j++) {
					returnValueDer += (BaseVector.get(j) - femInfluence[j]) * res2[j];
				}
			}

		}
		if (Double.isNaN(returnValue)) {
			returnValue = 0.0;
		}
		if (Double.isNaN(returnValueDer)) {
			returnValueDer = 0.0;
		}
		return new double[] { returnValue, returnValueDer };
	}

	public double calculateDerivaty(double x) {
		if ((xi.xi - xi.rho > x) || (xi.xi + xi.rho < x)) {
			return 0;
		}
		ArrayList<Double> BaseVector = getBaseVector(xi.xi, xi);
		int n = BaseVector.size();
		b = new double[n];
		// формуємо матрицю Грамма М
		A = new HashMatrixLUStyle(RowType.ARRAY_TYPE, n);
		for (int i = 0; i < NodeList.size(); i++) {
			Particle particle = NodeList.get(i);
			calculateAlfa(x, particle);
		}

		double val = func.calculate(x, xi.xi, xi.rho);
		// BaseVector = getBaseVector(x, new Particle(x, 0.0));
		boolean key = false;
		for (int i = 0; i < b.length; i++) {
			b[i] = BaseVector.get(i) * val;
			if (Math.abs(b[i]) > EPS) {
				key = true;
			}
		}

		double returnValue = 0.0;
		if (key) {
			LUFactorization lu = new LUFactorization(A);
			double[] res = lu.calculate(b);

			// val = func.evaluateDerivaty((x-xii)/rho);
			key = false;
			double[] tmp = new double[b.length];
			for (int j = 0; j < this.NodeList.size(); j++) { // по вузлах
				Particle xj = NodeList.get(j);
				val = func.calculateDerivaty(x, xj.xi, xj.rho);// func.evaluateDerivaty((x-xj.x)/xj.rho);
				BaseVector = this.getBaseVector(xj.xi, xj);
				double tmpVal = 0.0;
				for (int k = 0; k < res.length; k++) {// альфа на вектор Р і
														// вагу ...
					tmpVal += BaseVector.get(k) * res[k];
				}

				for (int k = 0; k < res.length; k++) {// число на вектор Р
					tmp[k] += BaseVector.get(k) * tmpVal * val;
				}
			}
			val = func.calculateDerivaty(x, xi.xi, xi.rho); // func.evaluateDerivaty((x-xii)/rho);
			BaseVector = this.getBaseVector(xi.xi, xi);
			for (int i = 0; i < b.length; i++) {
				b[i] = BaseVector.get(i) * val - tmp[i];
				if (Math.abs(b[i]) > EPS) {
					key = true;
				}
			}
			BaseVector = this.getBaseVectorDerivaty(x, new Particle(x, 0.0));
			double[] femInfluence = this.calculateDerivatyFEM(x);
			for (int j = 0; j < res.length; j++) {
				returnValue += (BaseVector.get(j) - femInfluence[j]) * res[j];
			}
			if (key) {
				double[] res2 = lu.calculate(b);

				BaseVector = this.getBaseVector(x, new Particle(x, 0.0));
				femInfluence = this.calculateFEM(x);
				for (int j = 0; j < res2.length; j++) {
					returnValue += (BaseVector.get(j) - femInfluence[j]) * res2[j];
				}
			}

		}
		if (Double.isNaN(returnValue)) {
			return 0;
		}
		// returnValue = calculateDerivatyAproximation(x);
		return returnValue;

	}

	public double calculateDerivatyAproximation(double x) {
		if ((xi.xi - xi.rho > x) || (xi.xi + xi.rho < x)) {
			return 0;
		}
		ArrayList<Double> BaseVector = getBaseVector(xi.xi, xi);
		int n = BaseVector.size();
		b = new double[n];
		// формуємо матрицю Грамма М
		A = new HashMatrixLUStyle(RowType.ARRAY_TYPE, n);
		for (int i = 0; i < NodeList.size(); i++) {
			Particle particle = NodeList.get(i);
			calculateAlfa(x, particle);
		}

		double val = func.calculate(x, xi.xi, xi.rho);
		// BaseVector = getBaseVector(x, new Particle(x, 0.0));
		boolean key = false;
		for (int i = 0; i < b.length; i++) {
			b[i] = BaseVector.get(i) * val;
			if (Math.abs(b[i]) > EPS) {
				key = true;
			}
		}

		double returnValue = 0.0;
		if (key) {
			LUFactorization lu = new LUFactorization(A);
			double[] res = lu.calculate(b);

			// BaseVector = getBaseVector(xi.xi, xi);
			BaseVector = getBaseVectorDerivaty(x, new Particle(x, 0.0));
			for (int i = 0; i < b.length; i++) {
				returnValue += BaseVector.get(i) * res[i];
			}
		}
		if (Double.isNaN(returnValue)) {
			return 0;
		}
		return returnValue;
	}

	public double[] calculateDerivatyFEM(double x) {
		ArrayList<Double> BaseVector = this.getBaseVector(x, xi);
		double[] res = new double[BaseVector.size()];
		double tmp = 0.0;
		for (int i = 0; i < femList.size(); i++) {
			IFEM1D fEMShapeFunction1D1 = femList.get(i);
			BaseVector = this.getBaseVector(fEMShapeFunction1D1.getXi(), new Particle(fEMShapeFunction1D1.getXi(), 0.0));
			tmp = fEMShapeFunction1D1.calculateDerivaty(x);
			for (int j = 0; j < res.length; j++) {
				res[j] += BaseVector.get(j) * tmp;
			}
		}
		return res;
	}

	public double[] calculateFEM(double x) {
		ArrayList<Double> BaseVector = this.getBaseVector(x, xi);
		double[] res = new double[BaseVector.size()];

//		if (x > 0.0) {
//			for (int i = 0; i < res.length; i++) {
//				res[i] = 0.0;
//			}
//			return res;
//		}

		double tmp = 0.0;
		for (int i = 0; i < femList.size(); i++) {
			IFEM1D fEMShapeFunction1D1 = femList.get(i);
			BaseVector = this.getBaseVector(fEMShapeFunction1D1.getXi(), new Particle(fEMShapeFunction1D1.getXi(), 0.0));
			tmp = fEMShapeFunction1D1.calculate(x);
			for (int j = 0; j < res.length; j++) {
				res[j] += BaseVector.get(j) * tmp;
			}
		}
		return res;
	}

	public ArrayList<Double> getBaseVector(double x, Particle xi) {
		ArrayList<Double> L = new ArrayList<Double>();
		for (int i = 0; i <= this.PolinomialDegree; i++) {
			L.add(Math.pow((xi.xi), i));
		}
		// L.add(Math.exp(100*xi.xi));
		return L;
	}

	public ArrayList<Double> getBaseVectorDerivaty(double x, Particle xi) {
		ArrayList<Double> L = new ArrayList<Double>();
		L.add(0.0);
		for (int i = 1; i <= this.PolinomialDegree; i++) {
			L.add(i * Math.pow((xi.xi), i - 1));
		}
		// L.add(100*Math.exp(100*xi.xi));
		return L;
	}

	public ArrayList<Double> getBaseVectorSecondDerivaty(double x, Particle xi) {
		ArrayList<Double> L = new ArrayList<Double>();
		L.add(0.0);
		L.add(0.0);
		for (int i = 2; i <= this.PolinomialDegree; i++) {
			L.add(i * (i - 1) * Math.pow((xi.xi), i - 2));
		}
		// L.add(Math.exp(xi.xi));
		return L;
	}

	@Override
	public double[] getBounds() {
		return new double[] { xi.xi - xi.rho, (xi.xi + xi.rho) };
	}

	@Override
	public Particle getXi() {
		return this.xi;
	}

	public void setNodeList(ArrayList<Particle> nodeList) {
		NodeList = nodeList;
	}

	private void calculateAlfa(double x, Particle xi) {
		ArrayList<Double> BaseVector = getBaseVector(x, xi);
		int n = BaseVector.size();
		for (int i = 0; i < n; i++) {
			double val = BaseVector.get(i) * func.calculate(x, xi.xi, xi.rho);
			for (int j = 0; j < n; j++) {
				A.setValue(i, j, A.getValue(i, j) + val * BaseVector.get(j));
			}
		}
	}

	public static void main(String[] args) {
		ArrayList<IFEM1D> fem = new ArrayList<IFEM1D>();
		fem.add(new FEMShapeFunction1D1(0.0, 1.0));
		fem.add(new FEMShapeFunction1D1(1.0, 1.0));
		// fem.add(new FEMShapeFunction1D1(0.0, 0.1));
		// fem.add(new FEMShapeFunction1D1(-0.1, 0.1));
		double a = 0.0, b = 1;
		int nn = 5;
		ArrayList<Particle> ls1D = new ArrayList<Particle>();
		int PolinomDegree = 2;
		double h = (b - a) / (double) nn;
		double rho = 2.01 * h;
		// rho = 0.5;
		for (int i = 0; i <= nn; i++) {
			ls1D.add(new Particle(a + h * i, rho));
		}
		BaseFunctionCoupled jmf = new BaseFunctionCoupled(PolinomDegree, ls1D.get(0), new CubicSpline(), fem);
		jmf.setNodeList(ls1D);

		jmf.calculate(0.0);

		double xxx = 0.0;
		XYSeries s = new XYSeries("", true, true);

		XYSeriesCollection col = new XYSeriesCollection();
		jmf = new BaseFunctionCoupled(PolinomDegree, ls1D.get(ls1D.size()-1), new CubicSpline(), fem);
		jmf.setNodeList(ls1D);
		jmf.calculate((0.9));
		for (Iterator<Particle> iterator = ls1D.iterator(); iterator.hasNext();) {

			Particle particle = iterator.next();
			s = new XYSeries("" + particle, true, true);
			BaseFunctionCoupled jmf1 = new BaseFunctionCoupled(PolinomDegree, particle, new CubicSpline(), fem);
			jmf1.setNodeList(ls1D);
			xxx = a - 0.5;
			// xxx = 0.0;
			System.err.println("=========");
			while (xxx <= 1.5 + 0.0001) {
				double tmp1 = jmf1.calculate(xxx);
				s.add(xxx, tmp1);
				System.err.println(xxx+"\t"+tmp1);
				xxx += 0.002;
			}
			col.addSeries(s);
		}

		for (IFEM1D ff : fem) {
			xxx = a - 0.5;
			s = new XYSeries("fem", true, true);
			while (xxx <= 1.5 + 0.0001) {
				double tmp1 = ff.calculate(xxx);
				s.add(xxx, tmp1);
				xxx += 0.002;
			}
			col.addSeries(s);

		}

		NewOkCancelDialog d = new NewOkCancelDialog(null, false, col);
		d.setLocationRelativeTo(null);
		d.setVisible(true);

		// HashMatrix2 A = new HashMatrix2(HashMatrix2.ARRAY_TYPE,
		// fem.size()+ls1D.size());
		// double[] B = new double[A.getN()];
		// for (int i = 0; i < fem.size(); i++) {
		// FEMShapeFunction1D1 fem1 = fem.get(i);
		// for (int j = 0; j < fem.size(); j++) {
		// A.setValue(i, j, fem.get(j).calculate(fem1.getXi()));
		// }
		// for (int j = 0; j <ls1D.size() ; j++) {
		// Particle particle = ls1D.get(j);
		// BaseFunctionCoupled bf = new BaseFunctionCoupled(PolinomDegree,
		// particle,new CubicSpline(),fem);
		// bf.setNodeList(ls1D);
		// A.setValue(i, j+fem.size(), bf.calculate(fem1.getXi()));
		// }
		// B[i] = fem1.getXi()*fem1.getXi();
		// }
		//
		// for (int i = 0; i < ls1D.size(); i++) {
		// Particle particle = ls1D.get(i);
		// for (int j = 0; j < fem.size(); j++) {
		// A.setValue(i+fem.size(), j, fem.get(j).calculate(particle.xi));
		// }
		// for (int j = 0; j <ls1D.size() ; j++) {
		// Particle p = ls1D.get(j);
		// BaseFunctionCoupled bf = new BaseFunctionCoupled(PolinomDegree, p,new
		// CubicSpline(),fem);
		// bf.setNodeList(ls1D);
		// A.setValue(i+fem.size(), j+fem.size(), bf.calculate(particle.xi));
		// }
		// B[i+fem.size()] = particle.xi * particle.xi;
		// }

		// LUFactorization lu = new LUFactorization(A);
		//
		// double[] res = lu.calculate(B);

		// for (int i = 0; i < res.length; i++) {
		// System.out.println(res[i]);
		// }
		//
		// col = new XYSeriesCollection();
		// s = new XYSeries("fem", true, true);
		// xxx = 0.0;
		// while(xxx<=b+0.0001){
		// // double tmp1 = jmf.calculate(xxx);
		// double r = 0.0;
		// for (int j = 0; j < fem.size(); j++) {
		// r+=fem.get(j).calculate(xxx) * res[j];
		// }
		// for (int j = 0; j <ls1D.size() ; j++) {
		// Particle p = ls1D.get(j);
		// BaseFunctionCoupled bf = new BaseFunctionCoupled(PolinomDegree, p,new
		// CubicSpline(),fem);
		// bf.setNodeList(ls1D);
		// r+= bf.calculate(xxx)*res[j+fem.size()];
		// }
		//
		// s.add(xxx,r);
		// // s2.add(xxx, (jmf.calculate(xxx) - jmf.calculate(xxx-h1))/h1);
		// xxx+=0.002;
		// }
		//
		// // col.addSeries(s2);
		// col.addSeries(s);
		// d = new NewOkCancelDialog(null, false, col);
		// d.setLocationRelativeTo(null);
		// d.setVisible(true);

	}
}
